﻿using Microsoft.Extensions.DependencyModel;
using Serilog;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using System.Text.RegularExpressions;
//using System.Web.Compilation;

namespace Ls.Utils
{
    public  static class ReflectionHelper
    {
        // 需要过滤的程序集
        //private const string assemblySkipLoadingPattern = "^System|^mscorlib|^Microsoft|^AjaxControlToolkit|^Antlr3|^Autofac|^AutoMapper|^Castle|^ComponentArt|^CppCodeProvider|^DotNetOpenAuth|^EntityFramework|^EPPlus|^FluentValidation|^ImageResizer|^itextsharp|^log4net|^MaxMind|^MbUnit|^MiniProfiler|^Mono.Math|^MvcContrib|^Newtonsoft|^NHibernate|^nunit|^Org.Mentalis|^PerlRegex|^QuickGraph|^Recaptcha|^Remotion|^RestSharp|^Rhino|^Telerik|^Iesi|^TestDriven|^TestFu|^UserAgentStringLibrary|^VJSharpCodeProvider|^WebActivator|^WebDev|^WebGrease";
        // 符合要求的程序集
        private const string assemblyRestrictToLoadingPattern = ".*";
        #region FindClassesOfType 查询所有子类
        /// <summary>
        /// 查询子类
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<Type> FindClassesOfType<T>()
        {
            return FindClassesOfType(typeof(T), true);
        }
        /// <summary>
        /// 查询子类
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="onlyConcreteClasses"></param>
        /// <returns></returns>
        public static List<Type> FindClassesOfType<T>(bool onlyConcreteClasses)
        {
            return FindClassesOfType(typeof(T), onlyConcreteClasses);
        }
        /// <summary>
        /// 查询子类
        /// </summary>
        /// <param name="assignTypeFrom">夫类型</param>
        /// <returns></returns>
        public static List<Type> FindClassesOfType(Type assignTypeFrom)
        {
            return FindClassesOfType(assignTypeFrom, true);
        }

        /// <summary>
        /// 查询子类
        /// </summary>
        /// <param name="assignTypeFrom">夫类型</param>
        /// <param name="onlyConcreteClasses">是否具体类</param>
        /// <returns></returns>
        public static List<Type> FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses)
        {
            List<Type> result = new List<Type>();
            IList<Assembly> assemblies = GetAssemblies();
            foreach (Assembly assembly in assemblies)
            {
                Type[] types = null;
                try
                {
                    types = assembly.GetTypes();
                }
                catch
                {
                    //Entity Framework 6 doesn't allow getting types (throws an exception)
                    //if (!ignoreReflectionErrors)
                    //{
                    //    throw;
                    //}
                }
                if (types != null)
                {
                    foreach (var t in types)
                    {
                        if (assignTypeFrom.IsAssignableFrom(t) || (assignTypeFrom.IsGenericTypeDefinition && DoesTypeImplementOpenGeneric(t, assignTypeFrom)))
                        {
                            if (!t.IsInterface)
                            {
                                if (onlyConcreteClasses)
                                {
                                    if (t.IsClass && !t.IsAbstract)
                                    {
                                        result.Add(t);
                                    }
                                }
                                else
                                {
                                    result.Add(t);
                                }
                            }
                        }
                    }
                }
            }
            return result;
        }


        #endregion
      
        /// <summary>
        /// 获取程序集
        /// </summary>
        /// <returns></returns>
        public static IList<Assembly> GetAssemblies()
        { 
            var assemblies = new List<Assembly>();
            // 获取运行时的程序集
            //IEnumerable assemblies = BuildManager.GetReferencedAssemblies();
            //if (LoadAppDomainAssemblies)
            GetAssembliesFromDependencyContext( assemblies);

            LoadMatchingAssemblies(assemblies, AppDomain.CurrentDomain.BaseDirectory);
            //LoadMatchingAssemblies(addedAssemblyNames, AppDomain.CurrentDomain.BaseDirectory);
            return assemblies;
        }

        /// <summary>
        /// Makes sure matching assemblies in the supplied folder are loaded in the app domain.
        /// </summary>
        private static void LoadMatchingAssemblies(List<Assembly>  assemblies, string directoryPath)
        {
            if (!string.IsNullOrEmpty(directoryPath) &&  !Directory.Exists(directoryPath))
            {
                return;
            }

            foreach (string dllPath in Directory.GetFiles(directoryPath, "*.dll"))
            {
                try
                {
                    Assembly assembly =  AssemblyLoadContext.Default.LoadFromAssemblyPath(dllPath);
                    if (Matches(assembly.FullName) && !assemblies.Any(a => string.Equals(a.FullName, assembly.FullName, StringComparison.OrdinalIgnoreCase)))
                    {
                        assemblies.Add(assembly);
                    }
                }
                catch (BadImageFormatException)
                {
                  Log.Debug("{ddlpath} is not a valid .net assembly.", dllPath);
                }
            }
        }

        private static void GetAssembliesFromDependencyContext(List<Assembly> assemblies) {

            foreach (CompilationLibrary compilationLibrary in DependencyContext.Default.CompileLibraries)
            {
                if (Matches(compilationLibrary.Name))
                {
                    Assembly assembly = null;

                    try
                    {
                        assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(compilationLibrary.Name));

                        if (!assemblies.Any(a => string.Equals(a.FullName, assembly.FullName, StringComparison.OrdinalIgnoreCase)))
                        {
                            assemblies.Add(assembly);
                        
                        }
                    } 
                    catch (Exception e)
                    { 
                    }
                }
            }
        } 

        /// <summary>
        ///  类型是否继承泛型
        /// </summary>
        /// <param name="type"></param>
        /// <param name="openGeneric"></param>
        /// <returns></returns>
        public static bool DoesTypeImplementOpenGeneric(Type type, Type openGeneric)
        {
            try
            {
                var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();
                foreach (var implementedInterface in type.FindInterfaces((objType, objCriteria) => true, null))
                {
                    if (!implementedInterface.IsGenericType)
                        continue;

                    var isMatch = genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition());
                    return isMatch;
                }
                return false;
            }
            catch
            {
                return false;
            }
        }

        public static bool Matches(string assemblyFullName)
        {
            return true;
            //return !Matches(assemblyFullName, assemblySkipLoadingPattern)
            //                && Matches(assemblyFullName, assemblyRestrictToLoadingPattern);
        }

        private static bool Matches(string assemblyFullName, string pattern)
        {
            return Regex.IsMatch(assemblyFullName, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        }

        /// <summary>Iterates all assemblies in the AppDomain and if it's name matches the configured patterns add it to our list.</summary>
        /// <param name="addedAssemblyNames"></param>
        /// <param name="assemblies"></param>
        //private static void AddAssembliesInAppDomain(List<string> addedAssemblyNames, List<Assembly> assemblies)
        //{
        //    foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        //    {
        //        if (Matches(assembly.FullName))
        //        {
        //            if (!addedAssemblyNames.Contains(assembly.FullName))
        //            {
        //                assemblies.Add(assembly);
        //                addedAssemblyNames.Add(assembly.FullName);
        //            }
        //        }
        //    }
        //}

        /// <summary>
        /// find assembly of a certain name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static Assembly FindAssembly(string name)
        {
            foreach (Assembly ass in GetAssemblies())
            {
                if (ass.GetName().Name.ToLower() == name.ToLower() || ass.FullName.ToLower() == name.ToLower())
                    return ass;
            } 
            return null;
        }


        public static Type GetType(string typeName) {
            if (typeName.IndexOf(",") >0)
            {
                string[] NameList = typeName.Split(',');
                var ass = FindAssembly(NameList[1].Trim());
                if (ass != null)
                {
                    return ass.GetType(NameList[0].Trim());
                }
            }
          
            return Type.GetType(typeName);
        }
    }
}
